package com.iretailer.report.quartz.sendReqularly;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.iretailer.report.dto.CameraFlowModel;
import com.iretailer.report.influxdb.service.InfluxdbFlowService;
import com.iretailer.report.model.*;
import com.iretailer.report.quartz.sendReqularly.model.FlowArray;
import com.iretailer.report.quartz.sendReqularly.model.ServerConfig;
import com.iretailer.report.quartz.sendReqularly.protocol.FTPSProtocol;
import com.iretailer.report.quartz.sendReqularly.protocol.FtpProtocol;
import com.iretailer.report.quartz.sendReqularly.protocol.HttpProtocol;
import com.iretailer.report.quartz.sendReqularly.protocol.SFTPProtocol;
import com.iretailer.report.quartz.sendReqularly.util.CommonUtil;
import com.iretailer.report.service.CategoriesService;
import com.iretailer.report.service.PhysicalzonesService;
import com.iretailer.report.service.SendRegularlyService;
import com.iretailer.report.util.BeanUtils;
import com.iretailer.report.util.StringUtil;
import com.iretailer.report.util.TimeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Auther Bennie
 * @Date 2020/6/15 10:39
 * @Description
 */

@Component
public class SendRegularlyCronService {
    private static final Logger log = LoggerFactory.getLogger(SendRegularlyCronService.class);
    private final SendRegularlyService sendRegularlyService;
    private final PhysicalzonesService physicalzonesService;
    private final InfluxdbFlowService influxdbFlowService;
    private final CategoriesService categoriesService;
    private final SendRegularlyConfig sendRegularlyConfig;
    private static final Pattern PATTERN = Pattern.compile("\\$\\{[0-9a-zA-Z]+\\}");
    private static final ParserContext PARSE_CONTEXT = new TemplateParserContext("${", "}");
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

    public SendRegularlyCronService(SendRegularlyService sendRegularlyService, PhysicalzonesService physicalzonesService,
                                    InfluxdbFlowService influxdbFlowService, CategoriesService categoriesService, SendRegularlyConfig sendRegularlyConfig) {
        this.sendRegularlyService = sendRegularlyService;
        this.physicalzonesService = physicalzonesService;
        this.influxdbFlowService = influxdbFlowService;
        this.categoriesService = categoriesService;
        this.sendRegularlyConfig = sendRegularlyConfig;
    }

    @Async(value = "sendRegularlyExecutor")
    public void executeSend(ZonedDateTime judgeZoneDateTime, List<SendRegularly> sendRegularlies) {
        if (sendRegularlies != null) {
            // 发送这4个区域数据
            Categories floor = categoriesService.selectCategoryByKeyName("Floor");
            Categories domain = categoriesService.selectCategoryByKeyName("Domain");
            Categories entrance = categoriesService.selectCategoryByKeyName("Entrance");
            Categories corridor = categoriesService.selectCategoryByKeyName("Corridor");
            List<String> zoneTypeIds = Arrays.asList(entrance.getId(), corridor.getId(), floor.getId(), domain.getId());
            for (SendRegularly s : sendRegularlies) {
                Map<String, String> startAndEndTime = CronTypeUtil.getStartAndEndTime(judgeZoneDateTime, s.getCronType());
                String startTime = startAndEndTime.get("StartTime");
                String endTime = startAndEndTime.get("EndTime");
                // 查找最近一次的日志，如果上一条没有成功则改变startTime，如果成功了则不做动作
                SendRegularlyLog latelyLog = sendRegularlyService.getLatelyLog(s.getId());
                if (latelyLog != null) {
                    // 如果没有成功则把之前没有成功的都发送
                    if (latelyLog.getStatus() == -1) {
                        startTime = latelyLog.getSendStartTime();
                    }
                }
                sendMassage(zoneTypeIds, s, startTime, endTime, true);
            }
        }
    }

    /***
     * 根据 sendRegularlyId 和 时间去手动触发上传
     * @param sendRegularlyId
     * @param startTime
     * @param endTime
     */
    public boolean uploadByTime(Long sendRegularlyId, String startTime, String endTime, Boolean includEnd) {
        includEnd = Objects.isNull(includEnd) ? true : includEnd;
        SendRegularly send = sendRegularlyService.getOne(new QueryWrapper<SendRegularly>().lambda().eq(SendRegularly::getId, sendRegularlyId));
        return upload(send, startTime, endTime, includEnd);
    }

    /***
     * 根据日志重新上传
     * @param log
     */
    public boolean uploadByLog(SendRegularlyLog log) {
        SendRegularly send = sendRegularlyService.getOne(new QueryWrapper<SendRegularly>().lambda().eq(SendRegularly::getId, log.getSendregularlyId()));
        return upload(send, log.getSendStartTime(), log.getSendEndTime(), true);
    }

    public boolean upload(SendRegularly send, String startTime, String endTime, Boolean includEnd) {
        // 发送这4个区域数据
        Categories entrance = categoriesService.selectCategoryByKeyName("Entrance");
        Categories corridor = categoriesService.selectCategoryByKeyName("Corridor");
        Categories floor = categoriesService.selectCategoryByKeyName("Floor");
        Categories domain = categoriesService.selectCategoryByKeyName("Domain");
        List<String> zoneTypeIds = Arrays.asList(entrance.getId(), corridor.getId(), floor.getId(), domain.getId());
        return sendMassage(zoneTypeIds, send, startTime, endTime, includEnd);
    }

    /***
     * 发送数据
     * @param zoneTypeIds 发送数据的物理区域类型
     * @param s  SendRegularly
     * @param startTime 发送数据的开始时间
     * @param endTime 发送数据的结束时间
     */

    public boolean sendMassage(List<String> zoneTypeIds, SendRegularly s, String startTime, String endTime, Boolean includEnd) {
        boolean result = false;
        String msg = "";
        try {

            //要发送的数据
            List<SendRegularlyMalls> sMallIds = sendRegularlyService.getSendRegularlyMalls(s.getId());
            List<String> mallIds = new ArrayList<>();
            if (sMallIds != null && sMallIds.size() > 0) {
                mallIds.addAll(sMallIds.stream().map(SendRegularlyMalls::getMallId).collect(Collectors.toList()));
            }

            List<PhysicalZoneMall> physicalZone = physicalzonesService.selectByMallIds_ZoneTypeIds(mallIds, zoneTypeIds);
            // 拼接数据

            //得到数据的时间组
            LocalDateTime startTime_ = TimeUtil.parse_LocalDateTime(startTime);
            LocalDateTime endTime_ = TimeUtil.parse_LocalDateTime(endTime);

            List<String> timesArray = TimeUtil.getPeriodStr(startTime_.toLocalDate(), endTime_.toLocalDate(), includEnd, startTime_.toLocalTime(), endTime_.toLocalTime(), s.getCronType(), "yyyy-MM-dd HH:mm:ss");
            List<FlowArray> sendData = new ArrayList<>();
            if (physicalZone != null) {
                physicalZone.forEach(p -> {
                    timesArray.forEach(t -> {
                        sendData.add(new FlowArray(t, CronTypeUtil.getEndTime(t, s.getCronType()), p.getMallCode(), p.getMallName(),
                                String.valueOf(p.getId()), p.getName(), p.getZoneTypeName(), 0, 0));
                    });
                });
            }
            List<CameraFlowModel> flow = influxdbFlowService.getPhysicalZones_PeriodFlow(physicalZone, startTime, endTime, s.getCronType());
            if (flow != null && flow.size() > 0) {
                Map<String, List<CameraFlowModel>> group_flow = flow.stream().collect(Collectors.groupingBy(CameraFlowModel::getZone_dateTime_Group));
                for (FlowArray f : sendData) {
                    String key = String.format("%s_%s", f.getPhysicalId(), f.getStartTime());
                    List<CameraFlowModel> flows = group_flow.get(key);
                    if (flows != null) {
                        flows.forEach(it -> {
                            f.setEnter(it.getCountenter() + f.getEnter());
                            f.setExit(it.getCountexit() + f.getExit());
                        });
                    }
                }
            }
            LocalDateTime now = LocalDateTime.now();
            ServerConfig config = JSON.parseObject(s.getConfigureMessage(), ServerConfig.class);
            String fileNameFormat = config.getFileNameFormat();
            String fileName;
            if (config.getFileType().equals("template") && StringUtil.notEmpty(fileNameFormat)) {
                String dateTimeFormatStr = "yyyyMMddHHmm";
                fileNameFormat = formatDateTimeByExpression("now", fileNameFormat, dateTimeFormatStr, now);
                fileNameFormat = formatDateTimeByExpression("startTime", fileNameFormat, dateTimeFormatStr, startTime_);
                fileNameFormat = formatDateTimeByExpression("endTime", fileNameFormat, dateTimeFormatStr, startTime_);
                fileName = fileNameFormat;
            } else {
                String dateTimeFormatStr = "yyyyMMddHHmmSSS";
                fileName = String.format("report_%s_%s" + (config.getFileType().equals("txt") ? ".txt" : ".csv"),
                        TimeUtil.localDateTime_format(now, dateTimeFormatStr), s.getId());
            }
            if ("HTTP".equals(config.getServerType())) {
                result = HttpProtocol.initAndSend(s, config, sendData);
            } else {
                String template = config.getTemplate();
                byte[] data;
                if (config.getFileType().equals("template") && StringUtil.notEmpty(template)) {
                    String dataByTemplate = getDataByTemplate(template, sendData);
                    data = StringUtil.notEmpty(config.getFileHeader()) ?
                            (config.getFileHeader() + "\r\n" + dataByTemplate).getBytes(sendRegularlyConfig.getCharsetName()) : dataByTemplate.getBytes(sendRegularlyConfig.getCharsetName());
                } else {
                    data = CommonUtil.Json2CSV(sendData, FlowArray.class).getBytes(sendRegularlyConfig.getCharsetName());
                }
                switch (config.getServerType()) {
                    case "FTP":
                        result = FtpProtocol.initAndSend(s, config, fileName, data);
                        break;
                    case "SFTP":
                        result = SFTPProtocol.initAndSend(s, config, fileName, data);
                        break;
                    case "FTPS":
                        result = FTPSProtocol.initAndSend(s, config, fileName, data);
                        break;
                }

            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            msg = e.getMessage();
        } finally {
            int status = -1;
            if (result) {
                status = 1;
                msg = "success";
            }
            sendRegularlyService.updateSendRegularlyMessage(new SendRegularlyLog(s.getId(), startTime, endTime, status, LocalDateTime.now(), msg));
        }
        log.info("{} {}", result, s.getDescription());
        return result;
    }

    private String getDataByTemplate(String template, List<FlowArray> sendData) {
        Field[] fields = BeanUtils.getFields(FlowArray.class);
        Map<String, Field> fieldMap = new HashMap<>();
        for (Field field : fields) {
            boolean accessible = field.isAccessible();
            if (!accessible) {
                field.setAccessible(true);
            }
            fieldMap.put(field.getName(), field);
            field.setAccessible(accessible);
        }
        Matcher matcher = PATTERN.matcher(template);
        Map<String, Field> filterFieldMap = new HashMap<>();
        while (matcher.find()) {
            Expression expression = EXPRESSION_PARSER.parseExpression(template.substring(matcher.start(), matcher.end()), PARSE_CONTEXT);
            Field field = fieldMap.get(expression.getExpressionString());
            if (field != null) {
                filterFieldMap.put("${" + expression.getExpressionString() + "}", field);
            }
        }
        return sendData.stream().map(flowArray -> {
            String rowData = template;
            for (Map.Entry<String, Field> FieldEntry : filterFieldMap.entrySet()) {
                Field value = FieldEntry.getValue();
                boolean accessible = value.isAccessible();
                if (!accessible) {
                    value.setAccessible(true);
                }
                Object o = null;
                try {
                    o = value.get(flowArray);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                value.setAccessible(accessible);
                rowData = rowData.replace(FieldEntry.getKey(), o == null ? "" : o.toString());
            }
            return rowData;
        }).collect(Collectors.joining("\r\n"));
    }

    private String formatDateTimeByExpression(String name, String fileNameFormat, String dateTimeFormatStr, LocalDateTime dateTime) {
        String dateTimeTemplate = "\\$\\{%s(:[^\\}]+)?\\}";
        Matcher m = Pattern.compile(String.format(dateTimeTemplate, name)).matcher(fileNameFormat);
        while (m.find()) {
            String eStr = fileNameFormat.substring(m.start(), m.end());
            int i = eStr.indexOf(":");
            if (i > 0) {
                dateTimeFormatStr = eStr.substring(i + 1, eStr.length() - 1);
            }
            fileNameFormat = fileNameFormat.replace(eStr, dateTime.format(DateTimeFormatter.ofPattern(dateTimeFormatStr)));
        }
        return fileNameFormat;
    }
}
